<?php
#
# dmBridge: a data access framework for CONTENTdm(R)
#
# Copyright © 2009, 2010, 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * Encapsulates a CONTENTdm(R) collection.
 *
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 */
class DMCollection extends DMModel implements DMURIAddressable {

	/** @var Array of DMCollections */
	private static $authorized = array();

	/** @var string Collection alias */
	private $alias;
	/** @var DMDateTime */
	private $date_search_begin_year;
	/** @var DMDateTime */
	private $date_search_end_year;
	/** @var string */
	private $description;
	/** @var array of DMFacetTerm objects */
	private $facets = array();
	/** @var array Array of DMDCElement objects, lazy-loaded */
	private $fields = array();
	/** @var array of DMDCElement objects */
	private $grid_view_fields = array();
	/** @var boolean */
	private $has_been_loaded = false;
	/** @var boolean */
	private $has_custom_object_view_prefs = false;
	/** @var boolean */
	private $has_custom_results_view_prefs = false;
	/** @var boolean */
	private $has_custom_search_view_prefs = false;
	/** @var DMURI */
	private $image_uri_512;
	/** @var boolean */
	private $is_redirecting_reference_urls;
	/** @var string Collection name, lazy-loaded by getName() */
	private $name;
	/** @var boolean */
	private $template_set_is_default = false;
	/** @var DMURI */
	private $overview_uri;
	/** @var int */
	private $template_set_id;
	/** @var array of DMObjectViewerDefinition objects */
	private $viewer_defs = array();

	/**
	 * @return An array of all collections in the CONTENTdm(R) system to which
	 * the current user has access, as DMCollection objects. The list of
	 * collections returned has no bearing on any restrictions set in the
	 * templates, if any.
	 * @since 0.1
	 */
	public static function getAuthorized() {
		if (count(self::$authorized) < 1) {
			foreach (dmGetCollectionList() as $c) {
				self::$authorized[] = DMCollectionFactory::getCollection($c['alias']);
			}
			usort(self::$authorized,
					array("DMCollection", "compareCollectionNames"));
		}
		return self::$authorized;
	}

	/**
	 * Used by usort() in getAuthorized().
	 */
	private static function compareCollectionNames(DMCollection $a,
		   DMCollection $b) {
		return strcasecmp($a->getName(), $b->getName());
	}

	/**
	 * @param string alias
	 * @since 0.4
	 */
	public static final function exists($alias) {
		foreach (dmGetCollectionList() as $c) {
			if ($c['alias'] == $alias) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @param string alias
	 */
	public static function getSanitizedAlias($alias) {
		return "/" . DMString::paranoid(trim($alias, "/"), array("-", "_"));
	}

	/**
	* To instantiate a generic collection, supply an alias with value of
	* <strong>/dmdefault</strong>.
	*
	* @param alias
	* @throws DMUnavailableModelException
	* @since 0.1
	*/
	public function __construct($alias) {
		$this->setAlias($alias);
		$this->load();
	}

	/**
	 * The return value of this method may change and should not be depended on.
	 *
	 * @return string The collection's alias
	 * @since 0.1
	 */
	public function __toString() {
		return $this->getAlias();
	}

	/**
	 * @param mixed obj
	 * @return Boolean
	 */
	public function equals($obj) {
		return (is_object($obj) && $obj instanceof DMCollection
				&& $this->getAlias() == $obj->getAlias());
	}

	/**
	 * @return string
	 * @since 0.1
	 */
	public function getAlias() {
		return $this->alias;
	}

	/**
	* @param string alias
	* @throws DMUnavailableModelException
	* @since 0.1
	*/
	private function setAlias($alias) {
		// @todo externalize this validation
		if (strlen($alias) < 1) {
			throw new DMUnavailableModelException(
				DMLocalizedString::getString("NO_ALIAS_SUPPLIED"));
		}
		else if (strlen($alias) < 2 or strlen($alias) > 30) {
			throw new DMUnavailableModelException(
				DMLocalizedString::getString("INVALID_ALIAS"));
		}
		$this->alias = $alias;
	}

	/**
	 * @return Boolean
	 */
	public function hasCustomObjectViewPrefs() {
		return $this->has_custom_object_view_prefs;
	}

	/**
	 * @param Boolean bool
	 */
	public function setHasCustomObjectViewPrefs($bool) {
		$this->has_custom_object_view_prefs = (bool) $bool;
	}

	/**
	 * @return Boolean
	 */
	public function hasCustomResultsViewPrefs() {
		return $this->has_custom_results_view_prefs;
	}

	/**
	 * @param Boolean bool
	 */
	public function setHasCustomResultsViewPrefs($bool) {
		$this->has_custom_results_view_prefs = (bool) $bool;
	}

	/**
	 * @return Boolean
	 */
	public function hasCustomSearchViewPrefs() {
		return $this->has_custom_search_view_prefs;
	}

	/**
	 * @param Boolean bool
	 */
	public function setHasCustomSearchViewPrefs($bool) {
		$this->has_custom_search_view_prefs = (bool) $bool;
	}

	/**
	 * @return int Four-digit year
	 */
	public function getDateSearchBeginYear() {
		return $this->date_search_begin_year;
	}

	/**
	 * @return int Four-digit year
	 */
	public function getDateSearchEndYear() {
		return $this->date_search_end_year;
	}

	/**
	 * @param int begin_year Four-digit year
	 * @param int end_year Four-digit year
	 * @throws DMIllegalArgumentException if $end_year is earlier than
	 * $begin_year
	 */
	public function setDateSearchRange($begin_year, $end_year) {
		if ($begin_year > $end_year) {
			throw new DMIllegalArgumentException(
					DMLocalizedString::getString("INVALID_DATE_RANGE"));
		}
		$this->date_search_begin_year = $begin_year;
		$this->date_search_end_year = $end_year;
	}

	/**
	 * @return void
	 */
	public function unsetDateSearchRange() {
		$this->date_search_begin_year = null;
		$this->date_search_end_year = null;
	}

	/**
	 * Loops through the collection's array of fields and returns the first
	 * date element it comes across.
	 *
	 * @return DMDCElement
	 * @since 0.3
	 */
	public function getDateSearchField() {
		foreach ($this->getFields() as $f) {
			if ($f->getType() == "DATE") {
				return $f;
			}
		}
	}

	/**
	 * @return string A brief description of the collection.
	 */
	public function getDescription() {
		return $this->description;
	}

	/**
	 * @param string desc A brief description of the collection.
	 */
	public function setDescription($desc) {
		$this->description = (string) $desc;
	}

	/**
	 * @return bool Whether the instance is of the default collection, of alias
	 * "/dmdefault"
	 */
	public function isDefault() {
		return ($this->getAlias() == "/dmdefault");
	}

	/**
	 * @param DMFacetTerm term
	 */
	public function addFacet(DMFacetTerm $term) {
		$this->facets[] = $term;
	}

	/**
	 * @return array Array of DMFacetTerm objects
	 */
	public function getFacets() {
		return $this->facets;
	}

	/**
	 * @return void
	 */
	public function unsetFacets() {
		$this->facets = array();
	}

	/**
	 * @param DMECElement element
	 */
	public function addField(DMDCElement $element) {
		$this->fields[] = $element;
	}

	/**
	 * @param string nick The field nickname that is used internally by
	 * CONTENTdm(R)
	 * @return The DMCollection's DMDCElement object corresponding to
	 * <code>$nick</code>, or null if a field with nickname <code>$nick</code>
	 * does not exist.
	 * @see DMDCElement::getVocabulary()
	 * @since 0.1
	 */
	public function getField($nick) {
		if (count($this->getFields()) < 1) {
			$this->instantiateFields();
		}
		foreach ($this->getFields() as $field) {
			if ($field->getNick() == $nick) {
				return $field;
			}
		}
		return null;
	}

	/**
	 * @return An array of all fields in the collection, as DMDCElement objects.
	 * If the collection is the default collection, returns an array of all DC
	 * fields. Field properties are as they are defined in the CONTENTdm(R)
	 * system. To retrieve custom field properties, as for e.g. grid view, use
	 * DMCollection::getResultsViewFields().
	 *
	 * @see getVocabulary()
	 * @since 0.1
	 */
	public function getFields() {
		return $this->fields;
	}

	/**
	 * @return void
	 */
	public function unsetFields() {
		$this->fields = array();
	}

	/**
	 * @param DMDCElement field
	 */
	public function addGridViewField(DMDCElement $field) {
		$this->grid_view_fields[] = $field;
	}

	/**
	 * @return array array of DMDCElement objects
	 */
	public function getGridViewFields() {
		return $this->grid_view_fields;
	}

	/**
	 * @return void
	 */
	public function unsetGridViewFields() {
		$this->grid_view_fields = array();
	}

	/**
	 * Returns the URI of a representative image for the collection. The image
	 * will be 512 pixels on its longest side.
	 * 
	 * @return DMURI
	 */
	public function getImage512URI() {
		return $this->image_uri_512;
	}

	/**
	 * @param DMURI uri The URI of a representative image for the collection.
	 * The image should be 512 pixels on its longest side and compressed for
	 * fast downloading.
	 */
	public function setImage512URI(DMURI $uri) {
		$this->image_uri_512 = $uri;
	}

	private function load() {
		if ($this->has_been_loaded) {
			return;
		}
		if ($this->getAlias() == "/dmdefault") {
			foreach (DMDCElement::getAll() as $f) {
				$this->addField($f);
			}
		} else {
			$cdm_defs = dmGetCollectionFieldInfo($this->getAlias());
			if (!is_array($cdm_defs)) {
				throw new DMUnavailableModelException(
					DMLocalizedString::getString("INSTANTIATE_NONEXISTENT_COLLECTION"));
			}
			foreach ($cdm_defs as $a) {
				$f = new DMDCElement($a['nick']);
				$f->setName($a['name']);
				$f->setControlled(($a['vocab'] > 0));
				$f->setCollection($this);
				$f->setSearchable(($a['search'] > 0));
				$f->setHidden(($a['hide'] > 0));
				$f->setDCNick($a['dc']);
				$f->setType($a['type']);
				$this->fields[] = $f;
			}
		}
		DMConfigXML::getInstance()->loadCollection($this);
		$this->has_been_loaded = true;
	}

	/**
	 * <p>Returns the name of the collection, as defined in the CONTENTdm(R)
	 * system.</p>
	 *
	 * <p>Note: If the current user does not have access to the collection, the
	 * collection will be named &quot;Untitled Collection,&quot; due to a
	 * bug/feature of the CONTENTdm(R) dmGetCollectionList() API function.</p>
	 *
	 * @return string
	 * @since 0.1
	 */
	public function getName() {
		if (empty($this->name)) {
			foreach (dmGetCollectionList() as $c) {
				if ($c['alias'] == $this->getAlias()) {
					$this->name = $c['name'];
				}
			}
			if (empty($this->name)) {
				$this->name = DMLocalizedString::getString(
						"DEFAULT_COLLECTION_NAME");
			}
		}
		return $this->name;
	}

	/**
	 * @return The total number of objects in the collection.
	 * @since 0.1
	 */
	public function getNumObjects() {
		$aliases = array($this->getAlias());
		$terms = array();
		$fields = array('title');
		$sort_fields = array();
		$limit = 1;
		$start = 1;
		$num_results = 0;
		dmQuery($aliases, $terms, $fields, $sort_fields, $limit,
			$start, $num_results, 1);
		return $num_results;
	}

	/**
	 * @return DMURI The overview URI defined in the Control Panel
	 */
	public function getOverviewURI() {
		return $this->overview_uri;
	}

	/**
	 * @param DMURI uri The overview URI defined in the Control Panel
	 */
	public function setOverviewURI(DMURI $uri) {
		$this->overview_uri = $uri;
	}

	/**
	 * @return Boolean
	 * @since 0.4
	 */
	public function isRedirectingReferenceURLs() {
		return $this->is_redirecting_reference_urls;
	}

	/**
	 * @param Boolean bool
	 */
	public function setRedirectingReferenceURLs($bool) {
		$this->is_redirecting_reference_urls = ($bool);
	}

	/**
	 * Returns an array of all DMDCElement objects that are to be displayed in
	 * grid view.
	 *
	 * @return array
	 * @see DMCollection::getFields()
	 * @since 0.1
	 */
	public function getResultsViewFields() {
		return $this->grid_view_fields;
	}

	/**
	 * @return DMTemplateSet
	 * @since 0.4
	 */
	public function getTemplateSet() {
		if ($this->template_set_id) {
			return new DMTemplateSet($this->template_set_id);
		}
		return null;
	}

	/**
	 * @param int id
	 */
	public function setTemplateSetID($id) {
		$this->setUsingDefaultTemplateSet(false);
		$this->template_set_id = $id;
	}

	/**
	 * @return DMInternalURI
	 */
	public function getURI() {
		return DMInternalURI::getResourceURI(
				DMBridgeComponent::HTTPAPI,
				"collections" . $this->getAlias());
	}

	/**
	 * @return Boolean
	 */
	public function isUsingDefaultTemplateSet() {
		return $this->template_set_is_default;
	}

	/**
	 * @param Boolean bool
	 */
	public function setUsingDefaultTemplateSet($bool) {
		if ($bool) {
			$this->template_set_id = null;
		}
		$this->template_set_is_default = ($bool);
	}

	/**
	 * @param DMObjectViewerDefinition vd
	 */
	public function addViewerDefinition(DMObjectViewerDefinition $vd) {
		$this->viewer_defs[] = $vd;
	}


	/**
	 * @param DMMediaType type
	 * @return DMObjectViewerDefinition
	 */
	public function getViewerDefinitionForMediaType(DMMediaType $type) {
		foreach ($this->viewer_defs as $def) {
			if ($def->getMediaType()->equals($type)) {
				return $def;
			}
		}
		return null;
	}

	/**
	 * @return array Array of DMObjectViewerDefinition objects
	 */
	public function getViewerDefinitions() {
		return $this->viewer_defs;
	}

	/**
	 * @return void
	 */
	public function unsetViewerDefinitions() {
		$this->viewer_defs = array();
	}

}

